package client

import (
	"bufio"
	"context"
	"fmt"
	"gRPC_Client/pbfiles"
	"github.com/golang/protobuf/ptypes"
	"github.com/google/uuid"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"io"
	"log"
	"math/rand"
	"os"
	"path/filepath"
	"strings"
	"time"
)

type LaptopClient struct {
	service pbfiles.LaptopServiceClient
}

func NewLaptopClient(conn *grpc.ClientConn) *LaptopClient {
	service := pbfiles.NewLaptopServiceClient(conn)
	return &LaptopClient{service: service}
}

func (laptopClient *LaptopClient) CreateLaptop(laptop *pbfiles.Laptop) {
	req := &pbfiles.CreateLaptopRequest{
		Laptop: laptop,
	}
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) // 模拟上下文取消错误
	defer cancel()
	res, err := laptopClient.service.CreateLaptop(ctx, req)

	if err != nil {
		st, ok := status.FromError(err)
		if ok && st.Code() == codes.AlreadyExists {
			log.Print("laptop already exists")
		} else {
			log.Fatal("cannot create laptop: ", err)
		}
		return
	}
	log.Printf("create laptop with id : %s", res.Id)
}

func (laptopClient *LaptopClient) UploadImage(imagePath string) {
	laptop := NewLaptop()
	laptopClient.CreateLaptop(laptop)
	file, err := os.Open(imagePath)
	if err != nil {
		log.Fatal("cannot open image file:", err)
	}
	defer file.Close()
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	stream, err := laptopClient.service.UploadImage(ctx)
	if err != nil {
		log.Fatal("cannot upload image: ", err)
	}
	req := &pbfiles.UploadImageRequest{
		Data: &pbfiles.UploadImageRequest_ImageInfo{
			ImageInfo: &pbfiles.ImageInfo{
				LaptopId:  laptop.GetId(),
				ImageType: filepath.Ext(imagePath),
			},
		},
	}
	err = stream.Send(req)
	if err != nil {
		log.Fatal("cannot send image info to server: ", err, stream.RecvMsg(nil))
	}
	reader := bufio.NewReader(file)
	buffer := make([]byte, 1024)

	for {
		n, err := reader.Read(buffer)
		if err == io.EOF {
			break
		}
		if err != nil {
			log.Fatal("cannot read chunk to buffer: ", err)
		}
		req := &pbfiles.UploadImageRequest{
			Data: &pbfiles.UploadImageRequest_ChunkData{
				ChunkData: buffer[:n],
			},
		}
		err = stream.Send(req)
		if err != nil {
			log.Fatal("cannot send chunk to server: ", err, stream.RecvMsg(nil))
		}
	}
	res, err := stream.CloseAndRecv()
	if err != nil {
		log.Fatal("cannot receive response: ", err)
	}

	log.Printf("image uploaded with id: %s, size: %d", res.GetId(), res.GetSize())
}

func (laptopClient *LaptopClient) SearchLaptop() {
	for i := 0; i < 10; i++ { //搜索之前随机创建一些
		laptop := NewLaptop()
		laptopClient.CreateLaptop(laptop)
	}
	filter := newFilter()

	log.Print("search filter :", filter)
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) // 模拟上下文取消错误
	defer cancel()

	req := &pbfiles.SearchLaptopRequest{
		Filter: filter,
	}
	resStream, err := laptopClient.service.SearchLaptop(ctx, req)
	if err != nil {
		log.Fatal("cannot search laptop: ", err)
	}
	for {
		res, err := resStream.Recv()
		if err == io.EOF {
			return
		}
		if err != nil {
			log.Fatal("cannot receive response: ", err)
		}
		laptop := res.GetLaptops()

		log.Print("- found:", laptop.GetId())
		log.Print(" + brand:", laptop.GetBrand())
		log.Print(" + name:", laptop.GetName())
		log.Print(" + cpu cores:", laptop.GetCpu().GetNumberCores())
		log.Print(" + cpu min ghz:", laptop.GetCpu().GetMinGhz())
		log.Print(" + ram:", laptop.GetMemory().GetValue(), laptop.GetMemory().GetUnit())
		log.Print(" + price:", laptop.GetPriceUsd())

		log.Println("-----------------------------------------------------------")
	}
}

func (laptopClient *LaptopClient) RatingLaptop() {
	n := 3
	laptopIDS := make([]string, n)
	for i := 0; i < n; i++ {
		laptop := NewLaptop()
		laptopIDS[i] = laptop.GetId()
		laptopClient.CreateLaptop(laptop)
	}
	scores := make([]float64, n)
	for {
		fmt.Print("rate laptop (y/n)?")
		var answer string
		fmt.Scan(&answer)

		if strings.ToLower(answer) != "y" {
			break
		}
		for i := 0; i < n; i++ {
			scores[i] = randomFloat64(20, 80)
		}
		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()

		stream, err := laptopClient.service.RateLaptop(ctx)
		if err != nil {
			log.Fatal("cannot rate laptop: ", err)
		}
		waitResponse := make(chan error)

		go func() {
			for {
				res, err := stream.Recv()
				if err == io.EOF {
					log.Print("no more responses")
					waitResponse <- nil
					return
				}
				if err != nil {
					waitResponse <- fmt.Errorf("cannot receive stream response: %v", err)
					return
				}
				log.Print("received response: ", res)
			}
		}()

		//send requests
		for i, laptopID := range laptopIDS {
			req := &pbfiles.RateLaptopRequest{
				LaptopId: laptopID,
				Score:    scores[i],
			}
			err := stream.Send(req)
			if err != nil {
				fmt.Errorf("cannot send stream request: %v --- %v", err, stream.RecvMsg(nil))
			}
			log.Print("send request: ", req)
		}
		err = stream.CloseSend()
		if err != nil {
			fmt.Errorf("cannot close send: %v", err)
		}
		err = <-waitResponse
		if err != nil {
			log.Fatal(err)
		}
	}
}

func newFilter() *pbfiles.Filter {
	return &pbfiles.Filter{
		MaxPriceUsd: 1000,
		MinCpuCores: 3,
		MinRam: &pbfiles.Memory{
			Value: uint64(2),
			Unit:  pbfiles.Memory_BYTE,
		},
		MinCpuGhz: 2.3,
	}
}
func NewLaptop() *pbfiles.Laptop {
	memGB := randomInt(2, 6)
	return &pbfiles.Laptop{
		Id:    uuid.New().String(),
		Brand: "Apple",
		Name:  "Macbook Pro",
		Cpu: &pbfiles.CPU{
			Brand:       "Intel",
			Name:        "",
			NumberCores: uint32(randomInt(2, 8)),
			NumThreads:  uint32(randomInt(2, 16)),
			MinGhz:      randomFloat64(2.2, 4.4),
			MaxGhz:      randomFloat64(4.4, 8.8),
		},
		Memory: &pbfiles.Memory{
			Value: 233,
			Unit:  pbfiles.Memory_GIGABYTE,
		},
		Gpus: []*pbfiles.GPU{&pbfiles.GPU{
			Brand:  "GPU brand",
			Name:   "GPu name",
			MinGhz: randomFloat64(2.2, 4.4),
			MaxGhz: randomFloat64(4.4, 8.8),
			Memory: &pbfiles.Memory{
				Value: uint64(memGB),
				Unit:  pbfiles.Memory_GIGABYTE,
			},
		}},
		Storages: []*pbfiles.Storage{&pbfiles.Storage{
			Driver: pbfiles.Storage_HDD,
			Memory: &pbfiles.Memory{
				Value: uint64(33),
				Unit:  pbfiles.Memory_TERABYTE,
			},
		}},
		Screen: &pbfiles.Screen{
			Resolution: &pbfiles.Screen_Resolution{
				Width:  uint32(14),
				Height: uint32(12),
			},
			Panel:      21,
			Multitouch: true,
		},
		Keyboard: &pbfiles.Keyboard{
			Layout:  pbfiles.Keyboard_QWERTZ,
			Backlit: true,
		},
		Weight: &pbfiles.Laptop_WeightKg{
			WeightKg: 1.5,
		},
		PriceUsd:    randomFloat64(200, 1000),
		ReleaseYear: uint32(2022),
		UpdateAt:    ptypes.TimestampNow(),
	}
}
func randomInt(min, max int) int {
	return min + rand.Int()%(max-min+1)
}

func randomFloat64(min, max float64) float64 {
	return min + rand.Float64()*(max-min)
}

func randomFloat32(min, max float32) float32 {
	return min + rand.Float32()*(max-min)
}
